﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using Hvam.SharePoint.Search.Language.Visitors;
using Hvam.SharePoint.Search.Query.Site.Models;
using Hvam.SharePoint.Search.Query.Site.QuerySteps;
using Hvam.SharePoint.Search.Query.Site.ResultSteps;
using Microsoft.Office.Server.Search.Query;
using Microsoft.SharePoint;

namespace Hvam.SharePoint.Search.Query.Site
{
    internal static class SiteSearchTools
    {
        public static SiteSearchResults GetResults(string query, Location location)
        {
            return GetResults(new SiteSearchQuery(query, location));
        }

        public static SiteSearchResults GetResults(SiteSearchQuery query)
        {
            // Run Query steps
            var querySteps = 
                new List<SiteSearchQueryStep>
                    {
                        new WriteQueryStep()
                    };

            query = querySteps.Aggregate(query, (q, step) => step.Process(q));

            // Generate query
            var results = new SiteSearchResults();

            var web = query.Web;

            var siteDataQuery = GetSiteDataQuery(query);

            if (siteDataQuery == null)
            {
                return results;
            }

            var steps =
                new List<SiteSearchResultStep>
                    {
                        new FieldExtractorResultStep(query),
                        new TaxIdFieldExtractorResultStep(query),
                        new UrlResultStep(query),
                        new IdResultStep(query), 
                        new SiteTitleResultStep(query),
                        new SizeResultStep(query),
                        new ImageUrlResultStep(query),
                        new WriteResultStep(query)
                    };

            var lists =
                (from list in XDocument.Parse(siteDataQuery.Lists).Descendants("List")
                let attribute = list.Attribute("ID")
                where attribute != null
                select new Guid(attribute.Value)).ToList();

            if (lists.Count() == 1)
            {
                // If one list, request list directly
                var list = web.Lists[lists.First()];

                // Handle folders
                var folderUrl = query.SiteRestriction;
                var folder = web.GetFolder(folderUrl);
                
                var q =
                    new SPQuery
                    {
                        ViewFields = siteDataQuery.ViewFields,
                        Query = siteDataQuery.Query,
                        RowLimit = siteDataQuery.RowLimit,
                        Folder = folder,
                        ViewAttributes = "Scope='Recursive'"
                        // DatesInUtc = 
                    };

                if (list.HasExternalDataSource && q.Query == "<Where></Where>")
                {
                    // Avoid exception "Error parsing CAML query - child not found."
                    q.Query = string.Empty;
                }

                var listItems = list.GetItems(q);
                results.TotalResults = listItems.Count;

                // Trim listitems
                var items = listItems.Cast<SPListItem>().Skip(query.StartItem - 1).Take(query.ItemsPerPage).ToList();
                foreach (var item in items)
                {
                    results.Results.Add(ProcessSteps(steps, item));
                }
            }
            else
            {
                // Do cross list query
                var dataTable = web.GetSiteData(siteDataQuery);

                results.TotalResults = dataTable.Rows.Count;

                var rows = dataTable.Rows.Cast<DataRow>().Skip(query.StartItem - 1).Take(query.ItemsPerPage).ToList();
                foreach (var row in rows)
                {
                    // TODO: optimize
                    var webId = new Guid(Convert.ToString(row["WebId"]));
                    var listId = new Guid(Convert.ToString(row["ListId"]));
                    var id = Convert.ToInt32(row["ID"]);

                    using (var w = query.Site.OpenWeb(webId))
                    {
                        var list = w.Lists[listId];
                        var item = list.GetItemByIdAllFields(id);

                        results.Results.Add(ProcessSteps(steps, item));
                    }
                }
            }
            
            return results;
        }

        private static Dictionary<string, string> ProcessSteps(IEnumerable<SiteSearchResultStep> steps, SPListItem item)
        {
            var result =
                new SiteSearchResult
                    {
                        ListItem = item
                    };

            return steps.Aggregate(result, (r, step) => step.Process(r)).Properties;
        }

        public static SPSiteDataQuery GetSiteDataQuery(SiteSearchQuery query)
        {
            // ViewFields

            // http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.spsitedataquery.viewfields.aspx
            var fields = query.RequestedFields.Values.ToList();
            var format = "<FieldRef ID=\"{0}\" Type=\"{2}\" Nullable=\"TRUE\" />";
            if (query.List != null && query.List.HasExternalDataSource)
            {
                // If external list, get fields by name
                format = "<FieldRef Name=\"{1}\" Type=\"{2}\" Nullable=\"TRUE\" />";
            }

            var fieldRefs =
                fields.ConvertAll(
                    field =>
                    string.Format(format, field.Id, field.InternalName,field.TypeAsString));

            var viewFields = string.Join(string.Empty, fieldRefs.ToArray());

            var web = query.Web;

            // Lists
            string lists;
            if (query.List != null)
            {
                lists = string.Format("<Lists><List ID=\"{0}\" /></Lists>", query.List.ID);
            }
            else
            {
                lists = "<Lists BaseType=\"0\" />";

                // Extract contenttype
                var propertyVisitor = new PropertyExtractorExpressionVisitor();
                var tuple = propertyVisitor.Extract(query.Expression, "contenttype");
                if (tuple.Item2.Any() && tuple.Item2.Count() == 1)
                {
                    var contentTypeName = tuple.Item2.First();

                    var contentType = web.AvailableContentTypes[contentTypeName];
                    if (contentType != null && contentType.Id.IsChildOf(SPBuiltInContentTypeId.Document))
                    {
                        lists = "<Lists BaseType=\"1\" />";
                    }
                }
            }

            // Query
            var visitor = new CamlExpressionVisitor();
            if (query.List != null)
            {
                visitor.ExternalDataSource = query.List.HasExternalDataSource;
            }
            var camlQuery = visitor.Translate(web.Site, query.AvailableFields, query.Expression);
            camlQuery = string.Format("<Where>{0}</Where>", camlQuery);
            camlQuery = GetSortQuery(web, query.SortList) + camlQuery;

            return
                new SPSiteDataQuery
                {
                    ViewFields = viewFields,
                    Lists = lists,
                    Webs = "<Webs Scope=\"Recursive\" />",
                    RowLimit = web.Site.WebApplication.MaxItemsPerThrottledOperation,
                    Query = camlQuery
                };
        }

        private static string GetSortQuery(SPWeb web, SortCollection sortList)
        {
            const string sortFieldTemplate = "<FieldRef ID=\"{0}\" Ascending=\"{1}\" />";

            if (sortList == null || sortList.Count == 0)
            {
                return string.Empty;
            }

            var sb = new StringBuilder();

            foreach (Sort item in sortList)
            {
                // TODO: optimize
                var sortfield = web.AvailableFields.OfType<SPField>().FirstOrDefault(p => string.Compare(p.InternalName, item.Property, true) == 0);
                if (sortfield != null)
                {
                    sb.AppendFormat(sortFieldTemplate, sortfield.Id, item.Direction == SortDirection.Ascending);
                }
            }

            return string.Format("<OrderBy>{0}</OrderBy>", sb);
        }
    }
}
